Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RoundRobinLoadBalancer reduce copy/resize for connection add/remove #1514

Merged
merged 2 commits into from
Apr 23, 2021

Conversation

Scottmitch
Copy link
Member

@Scottmitch Scottmitch commented Apr 23, 2021

Motivation:
RoundRobinLoadBalancer's Host class maintains a list of active
connections in a copy-on-write fashion. However when an item is added or
removed their is an additional copy operation due to initial state not
accounting for 1 more/less element.

Modifications:

  • Host class uses an array for copy-on-write storage and allocates
    exactly the required amount of space on add/remove.

Result:
Less resize/copy operations on connection established/closed.

Copy link
Member

@idelpivnitskiy idelpivnitskiy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice improvements!

} else {
Object[] newList = new Object[existing.length - 1];
System.arraycopy(existing, 0, newList, 0, i);
System.arraycopy(existing, i + 1, newList, i, newList.length - i);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a case for i == 0, then you can do a single arraycopy:

 System.arraycopy(existing, 1, newList, 0, newList.length);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT about this one?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it is worth it bcz arraycopy already does bounds checking. we could also specialize other cases (e.g. i == existing.length - 1) but lets save for followup investigation.

}
}
} else {
for (int i = 0; i < LARGE_LIST_SELECTION_ATTEMPTS; i++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep the previous logic in this case: (int) (connections.length * 0.75f)? 64 attempts looks low if users have 1000s of connections

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will remove the selection algorithm part of this PR and move to another PR.

int collisions = 0;
final int collisionThreshold = connections.length >>> 1;
while (i < connections.length) {
int index = rnd.nextInt(connections.length);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This algorithm is a bit involved and may have collisions. In the worst case it iterates over all connections. Consider taking a random value only for the first test and then continue in a loop over all connections.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will experiment with this, and I'll move the selection changes to another PR.

Motivation:
RoundRobinLoadBalancer's Host class maintains a list of active
connections in a copy-on-write fashion. However when an item is added or
removed their is an additional copy operation due to initial state not
accounting for 1 more/less element.

Modifications:
- Host class uses an array for copy-on-write storage and allocates
  exactly the required amount of space on add/remove.

Result:
Less resize/copy operations on connection established/closed.
@Scottmitch Scottmitch changed the title RoundRobinLoadBalancer adjust selection and connection storage RoundRobinLoadBalancer reduce copy/resize for connection add/remove Apr 23, 2021
} else {
Object[] newList = new Object[existing.length - 1];
System.arraycopy(existing, 0, newList, 0, i);
System.arraycopy(existing, i + 1, newList, i, newList.length - i);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT about this one?

@Scottmitch Scottmitch merged commit ba16bee into apple:main Apr 23, 2021
@Scottmitch Scottmitch deleted the rr_selection branch April 23, 2021 18:36

final Addr address;
private volatile List<C> connections = emptyList();
private volatile Object[] connections = EMPTY_ARRAY;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be ListenableAsyncCloseable

Comment on lines 367 to 377
for (;;) {
List<C> existing = this.connections;
if (existing == CLOSED_LIST) {
final Object[] existing = this.connections;
if (existing == CLOSED_ARRAY) {
return false;
}
ArrayList<C> connectionAdded = new ArrayList<>(existing);
connectionAdded.add(connection);
if (connectionsUpdater.compareAndSet(this, existing, connectionAdded)) {
Object[] newList = Arrays.copyOf(existing, existing.length + 1);
newList[existing.length] = connection;
if (connectionsUpdater.compareAndSet(this, existing, newList)) {
break;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do {} while(cU.compareAndSet()) would avoid the break

Comment on lines 380 to 403
connection.onClose().beforeFinally(() -> {
for (;;) {
final List<C> existing = connections;
if (existing == CLOSED_LIST) {
final Object[] existing = this.connections;
if (existing == CLOSED_ARRAY) {
break;
}
ArrayList<C> connectionRemoved = new ArrayList<>(existing);
if (!connectionRemoved.remove(connection) ||
connectionsUpdater.compareAndSet(this, existing, connectionRemoved)) {
int i = 0;
for (; i < existing.length; ++i) {
if (existing[i].equals(connection)) {
break;
}
}
if (i == existing.length) {
break;
} else {
Object[] newList = new Object[existing.length - 1];
System.arraycopy(existing, 0, newList, 0, i);
System.arraycopy(existing, i + 1, newList, i, newList.length - i);
if (connectionsUpdater.compareAndSet(this, existing, newList)) {
break;
}
}
}
}).subscribe();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps make this a standalone removeConnectionMethod() and call that in beforeFinally?

Comment on lines +383 to 385
if (existing == CLOSED_ARRAY) {
break;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really necessary as CLOSED_ARRAY is always empty.


final Addr address;
private volatile List<C> connections = emptyList();
private volatile Object[] connections = EMPTY_ARRAY;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How big is connections likely to be and how much contention is there likely to be? Could perhaps just use or extend CopyOnWriteArraySet

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its internal CopyOnWriteArrayList uses ReentrantLock that we prefer to avoid as it may content on the event-loop threads.

idelpivnitskiy pushed a commit that referenced this pull request May 6, 2021
…1514)

Motivation:
RoundRobinLoadBalancer's Host class maintains a list of active
connections in a copy-on-write fashion. However when an item is added or
removed their is an additional copy operation due to initial state not
accounting for 1 more/less element.

Modifications:
- Host class uses an array for copy-on-write storage and allocates
  exactly the required amount of space on add/remove.

Result:
Less resize/copy operations on connection established/closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants